大家還記得為什麼我們要作網站嗎?是因為我們要作 Line 聊天機器人啦。因為作 Line 所提供的 Line Messaging API Webhook 是透過 HTTP 協定來完成,所以我們需要弄一台網頁伺服器,讓 Line 有事情時可以透過 HTTP 協定來通知我們。
我們從第四天:認識 Webhook開始學習怎麼架網頁伺服器,直到第十九天:發布網站到 Heroku (續),終於成功作出一個能讓別人連線的網頁伺服器。
當然在這個過程中,我們學會的不只是會架網站。我們還學會了怎麼使用檔案總管、小黑框、Sublime Text、git 和 heroku。這中間並沒有浪費時間,因為所有的學習都會在這個時候用上。而你終於有能力可以開始來接 Line Messaging API 啦。
接下來的文章會採取一邊實作,一邊說明的方式。同時也會一邊複習之前學過的東西。而今天我們的目標是完成 Line Messaging API Webhook 的串接。
因為 Line 會傳東西給我們,所以 HTTP 請求方法會是 POST
,不會是以往我們使用的 GET
,我們先作一個能接受 POST
的網址出來:
在 config/routes.rb
加入一行:
post '/kamigo/webhook', to: 'kamigo#webhook'
在 app/controllers/kamigo_controller.rb
加入以下方法(注意,如果你的檔案中已經有 webhook 方法,就不用加):
def webhook
head :ok
end
head :ok
的意思是傳回 HTTP status code :200
以及空的 HTTP body。複習一下在第十二天:從瀏覽器認識 HTTP 協定有學過的,HTTP status code :200
表示成功。也就是說不管誰,傳什麼內容過來,我都會回一個成功。
用 rails server
開啟網頁伺服器,試試看直接在瀏覽器輸入網址 http://localhost:3000/kamigo/webhook:
Rails 返回的結果是 No route matches [GET] "/kamigo/webhook"
,意思是我們沒有設定 GET
連接到 /kamigo/webhook
,這表示我們直接在瀏覽器網址列輸入網址的話,是不可能產生出一個 POST
請求的。
有幾個方法可以作出 POST
請求,最常見的 POST
請求是在一個網頁上填表單按下送出的時候。我們不會採用這個方案,因為這個方案要寫程式。我們可以使用現成的工具來發出 POST 請求。
有一款工具叫作 POSTMAN:https://www.getpostman.com/
點一下 Windows
。
選 x64
就會開始下載,下載好就點開。
POSTMAN 是一款功能眾多的軟體,但我們用不到進階功能,所以我們就不註冊帳號了,點選下面圈起來的 Take me straight to the app. I'll create an account another time.
略過註冊,直接使用。
這是我們用不到的各種功能,直接關掉即可。
這個區塊是讓我們填入 HTTP 請求方法和網址的地方。我們要用 POST
方法,網址是 http://localhost:3000/kamigo/webhook
,填好按送出。
結果收到的 status code 是 422
,這是怎麼回事呢?
檢查一下 rails server 的小黑框會看到:
Started POST "/kamigo/webhook" for 127.0.0.1 at 2018-01-08 01:55:37 +0800
Processing by KamigoController#webhook as */*
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 2ms
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
...下略
為了防止世界被破壞,為了守護世界的和平...這是為了防範一種叫作 CSRF
的入侵手法。大概解釋一下他的原理:我可以作一個網頁,讓你在開啟網頁時,就讓你的瀏覽器對另一個網站發出 POST
請求,因為是從你的瀏覽器發出的,所以另一個網站如果沒有作檢查,可能就會中招。CSRF
可以代替你做很多事,比方說如果 Line 沒有防的話,我就可以讓你買 Line 貼圖送給我。
這是正常的情況,一個人類應該會先開啟一個表單頁,填好表之後再送出。
這是被入侵時會發生的情況,壞網頁會讓你的瀏覽器在不經過你同意的情況下,自己發出 POST
請求給另一個網站。
為了防止這個情形,我們的 Rails 網站會在人類在發出 GET
請求開啟表單頁時,就先給你一個號碼牌(CSRF token
),只有帶著正確號碼的號碼牌的人才能進 POST
請求,要不然就會被門口的警衛擋下來。但因為 Line 只會直接對我們發 POST
,所以我們不能使用表單號碼牌的機制,我們會使用的是 Line 給我們的 Channel access token
,這部分之後會再說明。
我們不能透過 Rails 內建的檢查機制來防治 CSRF
,所以我們得先關閉內建的 CSRF
檢查。
在 app/controllers/kamigo_controller.rb
中加入一行:
protect_from_forgery with: :null_session
通常這行會放在最上面,像這樣:
class KamigoController < ApplicationController
protect_from_forgery with: :null_session
...下略
弄好後存檔,此時再去按 POSTMAN 的 Send
,就會得到 Status: 200 OK,而我們的小黑框顯示的變成這樣:
Started POST "/kamigo/webhook" for 127.0.0.1 at 2018-01-08 02:41:37 +0800
Processing by KamigoController#webhook as */*
Can't verify CSRF token authenticity.
Completed 200 OK in 3ms
其中的 Can't verify CSRF token authenticity. 是在講說他沒有完成 CSRF 檢查。
我們上傳程式碼,上傳程式碼的三步驟:
git add .
git commit -m "新增 webhook"
git push heroku master
應該是會成功啦,我就不貼訊息了。
開啟 Line developer 後台 https://developers.line.me/console/:
點聊天機器人進入:
進入頁面後,你要找到上圖所在的 Messaging settings
區塊,其中有一項叫作 Webhook URL
,點一下右邊的筆進入編輯:
輸入我們剛剛作好的 POST 網址 https://people-all-love-kamigo.herokuapp.com/kamigo/webhook
,注意這裡你要填的是你的網站的網址,不要填到我的。
填好後按下 Update
。
就會在畫面上看到一個 Verify
按紐,按下去。
Success 成功!
今天就先到這,明天就能讓聊天機器人講話了。
想請問一下,這樣是什麼原因造成?(前面的postman可以成功)
明天的文會講怎麼除錯
對了,你可以用 POSTMAN 打一個 POST 到 https://oui-hat-student.herokuapp.com/kamigo/webhook
像這樣:
可以發現是 404,應該是 routes 沒設好。
最近真的都會打錯字,把post看成get
自己覺得很無言....
XD
想請問大大,出現以下錯誤要怎麼解決呢?
https://drive.google.com/open?id=1sO4EdSXfWa4ws6ic4shGECB1jgwE4z18
(還沒上傳到 heroku之前都正常,上傳後再POST一次就失敗LINE也出現錯誤)
你可以開兩個小黑框,一個輸入heroku logs -t
另一個輸入 heroku restart
,然後觀察一下訊息。看不懂的話直接貼上來也可以。
然後他出現了這些:
好像沒拍到重點QQ 要再往上一點
請把在 Gemfile 中的
gem 'pg'
改為
gem 'pg', '~> 0.21.0'
由於幾天前 pg 發布了新版本,而新版本似乎有點問題,所以我們需要指定安裝穩定的版本。如果我們不指定版本,就會安裝到有問題的最新版。
感謝大大,解決了~~~
我在測試時,也沒注意到要把 GET 改 POST
但後來出現status code 500,一直搞不定
今天把小黑窗的訊息翻譯一下... 原來我程式的第70行 多了一次END
語法錯誤,呵呵
Started POST "/kamigo/webhook" for 127.0.0.1 at 2018-01-26 18:38:44 +0800
SyntaxError (C:/Users/Nien/Desktop/粘粘的卡米狗/ironman/app/controllers/kamigo_controller.rb:70: syntax error, unexpected keyword_end, expecting end-of-input):
app/controllers/kamigo_controller.rb:70: syntax error, unexpected keyword_end, expecting end-of-input
以下這段程式碼的冒號位置為何往前一格就會爆炸呢??
以下是教學內的,顯示的錯誤碼是422
def webhook
head :ok
end
以下是我原本輸入的
顯示的錯誤碼是500
def webhook
head: ok
end
關於冒號 要解釋可以解釋很久XD
簡單講就是 head :ok 表示 head
是一個函數 :ok
則是他的第一個參數。
:ok
是一個 symbol,是一種類似 'ok'
的存在。
head: ok 的意思則是一個 hash { :head => ok }
的意思,電腦會認為 ok 是一個函數或者是一個變數。
我也是沒注意到要把 GET 改 POST
改正後就這錯誤
postman 500 internal server error
是哪裡錯了呢??!
這你要用 heroku logs -t 連去 server 看 log 才知道喔
我的也是出現500 internal server error
這是連上heroku logs -t之後的一些回覆:
2018-02-17T15:15:19.902582+00:00 heroku[web.1]: Stopping all processes with SIGTERM
2018-02-17T15:15:19.913855+00:00 app[web.1]: - Gracefully stopping, waiting for requests to finish
2018-02-17T15:15:19.916723+00:00 app[web.1]: - Goodbye!
2018-02-17T15:15:19.916921+00:00 app[web.1]: Exiting
2018-02-17T15:15:19.916680+00:00 app[web.1]: === puma shutdown: 2018-02-17 15:15:19 +0000 ===
2018-02-17T15:15:20.005257+00:00 heroku[web.1]: Process exited with status 143
2018-02-19T11:41:29.900771+00:00 heroku[web.1]: Unidling
2018-02-19T11:41:29.901242+00:00 heroku[web.1]: State changed from down to starting
2018-02-19T11:41:32.744051+00:00 heroku[web.1]: Starting process with command bin/rails server -p 35590 -e production
2018-02-19T11:41:36.108712+00:00 app[web.1]: => Booting Puma
2018-02-19T11:41:36.108735+00:00 app[web.1]: => Rails 5.1.5 application starting in production
2018-02-19T11:41:36.108737+00:00 app[web.1]: => Run rails server -h
for more startup options
2018-02-19T11:41:36.108751+00:00 app[web.1]: Puma starting in single mode...
2018-02-19T11:41:36.108760+00:00 app[web.1]: * Version 3.11.2 (ruby 2.3.4-p301), codename: Love Song
2018-02-19T11:41:36.108762+00:00 app[web.1]: * Min threads: 5, max threads: 5
2018-02-19T11:41:36.108763+00:00 app[web.1]: * Environment: production
2018-02-19T11:41:36.108863+00:00 app[web.1]: * Listening on tcp://0.0.0.0:35590
2018-02-19T11:41:36.109228+00:00 app[web.1]: Use Ctrl-C to stop
2018-02-19T11:41:36.522507+00:00 heroku[web.1]: State changed from starting to up
2018-02-19T11:41:38.034546+00:00 heroku[router]: at=info method=POST path="/kamigo/webhook" host=my-crazy-classmate.herokuapp.com request_id=be56ff81-db58-4b26-aa60-f8d759461fcb fwd="203.104.156.74" dyno=web.1 connect=1ms service=14ms status=500 bytes=308 protocol=https
2018-02-19T11:41:38.021933+00:00 app[web.1]: I, [2018-02-19T11:41:38.021816 #4] INFO -- : [be56ff81-db58-4b26-aa60-f8d759461fcb] Started POST "/kamigo/webhook" for 203.104.156.74 at 2018-02-19 11:41:38 +0000
2018-02-19T11:41:38.025388+00:00 app[web.1]: I, [2018-02-19T11:41:38.025321 #4] INFO -- : [be56ff81-db58-4b26-aa60-f8d759461fcb] Processing by KamigoController#webhook as HTML
2018-02-19T11:41:38.025560+00:00 app[web.1]: I, [2018-02-19T11:41:38.025496 #4] INFO -- : [be56ff81-db58-4b26-aa60-f8d759461fcb] Parameters: {"events"=>[{"replyToken"=>"00000000000000000000000000000000", "type"=>"message", "timestamp"=>1519040489199, "source"=>{"type"=>"user", "userId"=>"Udeadbeefdeadbeefdeadbeefdeadbeef"}, "message"=>{"id"=>"100001", "type"=>"text", "text"=>"Hello, world"}}, {"replyToken"=>"ffffffffffffffffffffffffffffffff", "type"=>"message", "timestamp"=>1519040489199, "source"=>{"type"=>"user", "userId"=>"Udeadbeefdeadbeefdeadbeefdeadbeef"}, "message"=>{"id"=>"100002", "type"=>"sticker", "packageId"=>"1", "stickerId"=>"1"}}], "kamigo"=>{"events"=>[{"replyToken"=>"00000000000000000000000000000000", "type"=>"message", "timestamp"=>1519040489199, "source"=>{"type"=>"user", "userId"=>"Udeadbeefdeadbeefdeadbeefdeadbeef"}, "message"=>{"id"=>"100001", "type"=>"text", "text"=>"Hello, world"}}, {"replyToken"=>"ffffffffffffffffffffffffffffffff", "type"=>"message", "timestamp"=>1519040489199, "source"=>{"type"=>"user", "userId"=>"Udeadbeefdeadbeefdeadbeefdeadbeef"}, "message"=>{"id"=>"100002", "type"=>"sticker", "packageId"=>"1", "stickerId"=>"1"}}]}}
2018-02-19T11:41:38.031930+00:00 app[web.1]: W, [2018-02-19T11:41:38.031866 #4] WARN -- : [be56ff81-db58-4b26-aa60-f8d759461fcb] Can't verify CSRF token authenticity.
2018-02-19T11:41:38.032266+00:00 app[web.1]: I, [2018-02-19T11:41:38.032206 #4] INFO -- : [be56ff81-db58-4b26-aa60-f8d759461fcb] Completed 500 Internal Server Error in 7ms
你有試過測試本機的情況嗎?就是連到 localhost:3000
請問卡米大
開啟後
這是Postman的部分
Sublime Text
Postman 要把 GET 改成 POST 哦
謝謝卡米大
卡卡米你好 請問以下問題該怎麼解決
https://drive.google.com/drive/folders/1hT7ZaWDMXlWQz4-wSfC4bhkwjkayh7GI
POSTMAN出現錯誤碼500
連到本機說YAML語法錯誤(?
heroku cli的安裝好像有問題 64bit32bit試過都不行 用指令好像也不行
還有一些看不太懂
.....感覺好多錯誤QQ
你的 YAML 語法錯誤可能是 空白 和 Tab 混用導致,可能要檢查一下。
你在 database.yaml 中指定使用的資料庫是 sqlite3,但是在 gemfile 裡面沒有安裝 sqlite3,這樣會有問題,你可以參考這一篇的設定:https://ithelp.ithome.com.tw/articles/10196781
git 方面倒是沒問題
你的 heroku 似乎還沒有正確設定,這導致 git push heroku master 不能正常運作。
可以使用 git remote add heroku https://git.heroku.com/你的heroku app名稱.git
這個指令新增遠端節點。
如果你不知道你的 heroku app 叫什麼名字,開啟這個網頁:https://dashboard.heroku.com/apps
應該會看到一個 app,如果你沒看到,那你就得新增一個。
sqlite3 OK了謝謝~
語法還沒看
heroku還是有問題
我又重新加了一個新的app(i-like-kamigo),好像也傳不上去
而且還出現舊的名稱(舊的已刪除)
看之前有人問的問題
要用heroku git:remote -a APP名稱把憑證洗掉
我也洗了 請問是什麼問題呢
用 git remote -v 然後把結果貼上來看看
可以登入,但驗證時出現windows視窗 再輸入一次帳密(?)好像就出問題 還是憑證還要再另外輸入什麼東西
試試看
echo %USERPROFILE%
echo %HOME%
set HOME=%USERPROFILE%
echo %HOME%
看看結果是什麼
是在小黑框用嗎 好像沒發生什麼 和之前出現的狀況一樣
這樣做完之後再去用 git push heroku master
呢?
跟之前一樣跳出這個
驗證輸入我的heroku帳密 然後也一樣出現
我是參考這篇:https://github.com/heroku/legacy-cli/issues/1488
看起來是你的 git 沒有找到 heroku 的 token。
文章裡面說你應該會有一個檔案叫作 _netrc
,裡面寫著帳密,如果在彈出的對話框裡面使用者名稱留白,密碼則是填入 _netr
的密碼,就會成功。但這一切應該是自動化的。
喔喔喔喔喔可以pushㄌ!!!!!!感動!!!
請問第一個warning會有影響嗎(下面還有三個warning我有看到第19篇說先不管它)
如果網站能正常打開的話 這個應該是沒有關係的
好的謝謝你~可以繼續往下了哈哈
恭喜
原本想直接刪除提問的 因為已經解決了
但我找不到刪除提問的按鈕..
後來去休息一下 心平氣和地回來全部再重看一次
發現是沒有上傳最後的程式碼... 不好意思打擾了-/-
好哦~好哦~
你好 我也是遇到一樣的問題
The webhook returned an invalid HTTP status code. (The expected status code is 200.)
檢查過程式碼還是不知道哪邊出了問題!
請問大大該如何解決呢 感謝
那你有上傳程式碼嗎?XD
有哦有哦
但還是不知道為什麼無法QQ
大大麻煩幫我解決問題我真解決不了,但我看到我的終端機的訊息是有問題的但我不知道怎解決
你有 push code 嗎
大大因為我真找不到問題在哪,所以我從建立了一個專案 只針對webhook測試,然後還是在終端機會出現一樣的問題
原本沒有root那行的時候我試過也是不行,然後我真的都有特別留意上傳程式碼,到heroku,可是真的還是不行
是說 有這行應該不會影響結果吧?
為什麼 404 Not Found ?
你的網址打錯了所以 404
錯在網址的最前面
用好了~
謝謝~
https://imgur.com/a/7cXC3a8
https://imgur.com/a/UjEoM3A
https://imgur.com/a/DmuX9Wd
https://imgur.com/a/eW63dVL
https://imgur.com/a/dviE1wd
打小了神麼令http://localhost:3000/kamigo/webhook 404
你在 postman 所使用的 method 應該要用 POST,但是你用 GET
嗯,謝卡卡米
嗯,謝卡卡米
卡米大請問一下我哪一個地方出了問題
我看好幾遍了還是找不到
只能請求大大幫忙了
問題已自行解決
發現是
def webhook
head :ok
end
這裡打在下面跟上面重複到了
請問我的問題在哪裡
require 'line/bot'
require 'net/http'
class KamigoController < ApplicationController
protect_from_forgery with: :null_session
def webhook
reply_text = learn(channel_id, received_text)
# 關鍵字回覆
reply_text = keyword_reply(channel_id, received_text) if reply_text.nil?
# 推齊
reply_text = echo2(channel_id, received_text) if reply_text.nil?
# 記錄對話
save_to_received(channel_id, received_text)
save_to_reply(channel_id, reply_text)
# 傳送訊息到 line
response = reply_to_line(reply_text)
# 回應 200
head :ok
end
def channel_id
source = params['events'][0]['source']
source['groupId'] || source['roomId'] || source['userId']
end
def save_to_received(channel_id, received_text)
return if received_text.nil?
Received.create(channel_id: channel_id, text: received_text)
end
def save_to_reply(channel_id, reply_text)
return if reply_text.nil?
Reply.create(channel_id: channel_id, text: reply_text)
end
def echo2(channel_id, received_text)
# 如果在 channel_id 最近沒人講過 received_text,卡米狗就不回應
recent_received_texts = Received.where(channel_id: channel_id).last(5)&.pluck(:text)
return nil unless received_text.in? recent_received_texts
# 如果在 channel_id 卡米狗上一句回應是 received_text,卡米狗就不回應
last_reply_text = Reply.where(channel_id: channel_id).last&.text
return nil if last_reply_text == received_text
received_text
end
def received_text
message = params['events'][0]['message']
message['text'] unless message.nil?
end
def learn(channel_id, received_text)
#如果開頭不是 關鍵字; 就跳出
return nil unless received_text[0..6] == '關鍵字;'
received_text = received_text[7..-1]
semicolon_index = received_text.index(';')
# 找不到分號就跳出
return nil if semicolon_index.nil?
keyword = received_text[0..semicolon_index-1]
message = received_text[semicolon_index+1..-1]
KeywordMapping.create(channel_id: channel_id, keyword: keyword, message: message)
'好哦~好哦~'
end
def keyword_reply(channel_id, received_text)
message = KeywordMapping.where(channel_id: channel_id, keyword: received_text).last&.message
return message unless message.nil?
KeywordMapping.where(keyword: received_text).last&.message
end
def reply_to_line(reply_text)
return nil if reply_text.nil?
# 取得 reply token
reply_token = params['events'][0]['replyToken']
# 設定回覆訊息
message = {
type: 'text',
text: reply_text
}
# 傳送訊息
line.reply_message(reply_token, message)
end
def line
@line ||= Line::Bot::Client.new { |config|
config.channel_secret = 'f251c5cb2d4b65ed2b05ad472ca134dc'
config.channel_token = 'zU9Hv+7iprvAN8jM7TRxXAPhVpjj2S0XGtmalUkk7v87H8P+myuiPnHRDIMmazcKFvzTDw3fji40x43H+kCEAcWKm/iAmaHXqgN4t0geBKSp6mKxE0yEcQdEad01uzl8YaFrCQeROkoLbztT4WirXQdB04t89/1O/w1cDnyilFU='
}
end
def eat
render plain: "吃土啦"
end
def request_headers
render plain: request.headers.to_h.reject{ |key, value|
key.include? '.'
}.map{ |key, value|
"#{key}: #{value}"
}.sort.join("\n")
end
def response_headers
response.headers['5566'] = 'QQ'
render plain: response.headers.to_h.map{ |key, value|
"#{key}: #{value}"
}.sort.join("\n")
end
def request_body
render plain: request.body
end
def show_response_body
puts "===這是設定前的response.body:#{response.body}==="
render plain: "虎哇花哈哈哈"
puts "===這是設定後的response.body:#{response.body}==="
end
def sent_request
uri = URI('http://localhost:3000/kamigo/eat')
http = Net::HTTP.new(uri.host, uri.port)
http_request = Net::HTTP::Get.new(uri)
http_response = http.request(http_request)
render plain: JSON.pretty_generate({
request_class: request.class,
response_class: response.class,
http_request_class: http_request.class,
http_response_class: http_response.class
})
end
def translate_to_korean(message)
"#{message}油~"
end
end
我不知道你想幹嘛
我的line連不道我的Webhook 一直顯示500
Rails s 顯示 kamigo_controller 第29行的channel_id 第8行的Webhook 有問題 我對了很多次我都找不到
請問你的第 29 行跟第 8 行是哪一行?
我重新來過後就OK了...
postman出現200,上傳至Heroku還是出錯,是不是我的server也沒有支援https,那要怎麼辦?
請問可以幫忙解決問題嗎!!!!!!!!!!
按照步驟做完,但網頁出跑不出來!!!!!!!!
直接用瀏覽器開啟網址時,發出的 request 是 GET 類型的 request。
而我們的 server 在收 webhook 時,只接受 POST 類型的 request,所以你找不到網頁是正常現象。
好的,謝謝您的回答
請問如果使用heroku logs -t出現這個問題
2019-08-12T13:31:44.300232+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=POST path="/janice/webhook" host=janice-linebot.herokuapp.com request_id=173d7493-4703-421f-aa16-48baecdf0279 fwd="203.104.156.75" dyno= connect= service= status=503 bytes= protocol=https
line那邊點選verify一直不能成功
要怎麼解決?
heroku restart
請問後續怎麼解決呢?目前也遇到一樣的狀況@@
可參考 https://hammer1007.blogspot.com/2021/05/linebotheroku.html
看error message